package dubrouski.segmentation;

import dubrouski.csc.Colors;
import dubrouski.triangulation.Vector3d;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Stanislau Dubrouski
 */
public class CentroidsCalculatorImpl implements CentroidsCalculator {


    public List<List<Vector3d>> computeCentroids(List<Colors> colors, int[][][] image) {

        List<List<Vector3d>> result = new ArrayList<List<Vector3d>>();

        for (Colors color : colors) {
            int labelsCreated = 0;

            short[][][][] labels = new short[colors.size()][image.length][image[0].length][image[0][0].length];

            List<Vector3d> centroids = new ArrayList<Vector3d>();
            SeedsStack seeds = new SeedsStack(500);

            for (int x = 0; x < image.length; x++) {
                for (int y = 0; y < image[0].length; y++) {
                    for (int z = 0; z < image[0][0].length; z++) {
                        if (pixelCanBeLabeled(x, y, z, color, colors, image, labels)) {
                            seeds.push(x, y, z);
                            labelsCreated++;
                        } else {
                            continue;
                        }

                        int[] currentComponentInfo = new int[4];

                        while (true) {
                            int nextX = seeds.popX();
                            if (nextX == -1) {
                                break;
                            }
                            int nextY = seeds.popY();
                            int nextZ = seeds.popZ();

                            int[] partialComponentInfos = fillLine3d(nextX, nextY, nextZ, color, colors, labelsCreated, image, labels, seeds);
                            currentComponentInfo[SUM_X_VALUES_POS] += partialComponentInfos[SUM_X_VALUES_POS];
                            currentComponentInfo[SUM_Y_VALUES_POS] += partialComponentInfos[SUM_Y_VALUES_POS];
                            currentComponentInfo[SUM_Z_VALUES_POS] += partialComponentInfos[SUM_Z_VALUES_POS];
                            currentComponentInfo[SUM_VALUES] += partialComponentInfos[SUM_VALUES];
                        }

                        Vector3d currentCentroid = new Vector3d(
                                (float) currentComponentInfo[SUM_X_VALUES_POS] / currentComponentInfo[SUM_VALUES],
                                (float) currentComponentInfo[SUM_Y_VALUES_POS] / currentComponentInfo[SUM_VALUES],
                                (float) currentComponentInfo[SUM_Z_VALUES_POS] / currentComponentInfo[SUM_VALUES]);

                        centroids.add(currentCentroid);
                    }
                }
            }

            result.add(centroids);
        }

        return result;
    }
    
    
    private static int[] fillLine3d(int x, int y, int z, Colors color, List<Colors> colors,
            int label, int[][][] image, short[][][][] labels, SeedsStack seeds) {
        boolean seedPlantedTop = false;     //y--
        boolean seedPlantedBottom = false;  //y++
        boolean seedPlantedLeft = false;    //z--
        boolean seedPlantedRight = false;   //z++

        int[] result = new int[4];

        setPixelLabel(x, y, z, color, colors, label, labels);
        int pColor = getColor(x, y, z, color, image);
        result[SUM_X_VALUES_POS] += x * pColor;
        result[SUM_Y_VALUES_POS] += y * pColor;
        result[SUM_Z_VALUES_POS] += z * pColor;
        result[SUM_VALUES] += pColor;

        seedPlantedTop = plantSeedTop(x, y, z, color, colors, image, labels, seeds);
        seedPlantedBottom = plantSeedBottom(x, y, z, color, colors, image, labels, seeds);
        seedPlantedLeft = plantSeedLeft(x, y, z, color, colors, image, labels, seeds);
        seedPlantedRight = plantSeedRight(x, y, z, color, colors, image, labels, seeds);

        int index = x;
        int step = 1;
        boolean goingForwards = true;
        while ((index < image.length - 1) && (index > 0)) {
            //bottom
            if (!pixelCanBeLabeled(index, y + 1, z, color, colors, image, labels)) {
                seedPlantedBottom = false;
            } else {
                if (!seedPlantedBottom) {
                    seedPlantedBottom = plantSeedBottom(index, y, z, color, colors, image, labels, seeds);
                }
            }
            //top
            if (!pixelCanBeLabeled(index, y - 1, z, color, colors, image, labels)) {
                seedPlantedTop = false;
            } else {
                if (!seedPlantedTop) {
                    seedPlantedTop = plantSeedTop(index, y, z, color, colors, image, labels, seeds);
                }
            }
            //left
            if (!pixelCanBeLabeled(index, y, z - 1, color, colors, image, labels)) {
                seedPlantedLeft = false;
            } else {
                if (!seedPlantedLeft) {
                    seedPlantedLeft = plantSeedLeft(index, y, z, color, colors, image, labels, seeds);
                }
            }
            //right
            if (!pixelCanBeLabeled(index, y, z + 1, color, colors, image, labels)) {
                seedPlantedRight = false;
            } else {
                if (!seedPlantedRight) {
                    seedPlantedRight = plantSeedRight(index, y, z, color, colors, image, labels, seeds);
                }
            }

            //step forward
            if (pixelCanBeLabeled(index + step, y, z, color, colors, image, labels)) {

                setPixelLabel(index + step, y, z, color, colors, label, labels);

                pColor = getColor(index + step, y, z, color, image);
                result[SUM_X_VALUES_POS] += (index + step) * pColor;
                result[SUM_Y_VALUES_POS] += y * pColor;
                result[SUM_Z_VALUES_POS] += z * pColor;
                result[SUM_VALUES] += pColor;
                index += step;
            } else if (goingForwards) {
                index = x;
                goingForwards = false;
                step = -1;
            } else {
                index += step;
                break;
            }
        }

        return result;
    }
    
    private static boolean plantSeedBottom(int x, int y, int z, Colors color, List<Colors> colors,
            int[][][] image, short[][][][] labels, SeedsStack seeds) {
        if (y < image[0].length - 1) {
            if (pixelCanBeLabeled(x, y + 1, z, color, colors, image, labels)) {
                seeds.push(x, y + 1, z);
                return true;
            }
        }
        return false;
    }

    private static boolean plantSeedTop(int x, int y, int z, Colors color, List<Colors> colors,
            int[][][] image, short[][][][] labels, SeedsStack seeds) {
        if (y > 0) {
            if (pixelCanBeLabeled(x, y - 1, z, color, colors, image, labels)) {
                seeds.push(x, y - 1, z);
                return true;
            }
        }
        return false;
    }

    private static boolean plantSeedLeft(int x, int y, int z, Colors color, List<Colors> colors,
            int[][][] image, short[][][][] labels, SeedsStack seeds) {
        if (z > 0) {
            if (pixelCanBeLabeled(x, y, z - 1, color, colors, image, labels)) {
                seeds.push(x, y, z - 1);
                return true;
            }
        }
        return false;
    }

    private static boolean plantSeedRight(int x, int y, int z, Colors color, List<Colors> colors,
            int[][][] image, short[][][][] labels, SeedsStack seeds) {
        if (z < image[0][0].length - 1) {
            if (pixelCanBeLabeled(x, y, z + 1, color, colors, image, labels)) {
                seeds.push(x, y, z + 1);
                return true;
            }
        }
        return false;
    }

    private static boolean pixelCanBeLabeled(int x, int y, int z, Colors color, List<Colors> colors,
            int[][][] image, short[][][][] labels) {
        if (x >= image.length || x < 0 || y >= image[0].length || y < 0 || z < 0 || z >= image[0][0].length) {
            return false;
        }
        return (!isPixelBackground(image[x][y][z], color) && !isPixelLabeled(x, y, z, color, colors, labels));
    }

    private static boolean isPixelBackground(int pixel, Colors color) {
        int result = 0;
        if (color == Colors.RED) {
            result = (int) (pixel & 0xff0000) >> 16;
        }
        if (color == Colors.GREEN) {
            result = (int) (pixel & 0x00ff00) >> 8;
        }
        if (color == Colors.BLUE) {
            result = (int) (pixel & 0x0000ff);
        }
        return result == 0;
    }

    private static boolean isPixelLabeled(int x, int y, int z, Colors color, List<Colors> colors, short[][][][] labels) {
        if (getPixelLabel(x, y, z, color, colors, labels) != 0) {
            return true;
        } else {
            return false;
        }
    }

    
    private static int getPixelLabel(int x, int y, int z, Colors color, List<Colors> colors, short[][][][] labels) {
        return labels[colors.indexOf(color)][x][y][z];
    }

    private static void setPixelLabel(int x, int y, int z, Colors color, List<Colors> colors, int label, short[][][][] labels) {
        labels[colors.indexOf(color)][x][y][z] = (short) label;
    }

    private static int getColor(int x, int y, int z, Colors color, int[][][] image) {
        int result = 0;
        if (color == Colors.RED) {
            result = (int) (image[x][y][z] & 0xff0000) >> 16;
        }
        if (color == Colors.GREEN) {
            result = (int) (image[x][y][z] & 0x00ff00) >> 8;
        }
        if (color == Colors.BLUE) {
            result = (int) (image[x][y][z] & 0x0000ff);
        }
        return result;
    }

}
